//
// Created by vivi on 27/07/2017.
//

#ifndef HAIRCORE_HAIRDFTLSIMULATOR_H
#define HAIRCORE_HAIRDFTLSIMULATOR_H

#include <vector>
#include "HairSimulator.h"
#include "SDF.h"

namespace XRwy {
    struct HairDFTLConfiguration {
        bool enable_body_collision;
        bool enable_hair_stiction;

        float filter_alpha; //use in the filter kernel

        size_t npass;

        float damping;
        float k;
        Eigen::Vector3f ext_acc;

        //use in stiction
        float kr;
        size_t max_replusion_link;
        float dr;
        float invalid_dr;
    };

    struct HairDFTLParticleInfo {
        size_t strand_idx;
        size_t rel_idx;

        inline bool operator==(const HairDFTLParticleInfo &rhs) const {
            return (strand_idx == rhs.strand_idx && rel_idx == rhs.rel_idx);
        }
    };

    struct HairDFTLStictionLinkInfo {
        HairDFTLParticleInfo particles[2];

        HairDFTLStictionLinkInfo(const HairDFTLParticleInfo &x1, const HairDFTLParticleInfo &x2) {
            particles[0] = x1;
            particles[1] = x2;
        }

        inline bool operator==(const HairDFTLStictionLinkInfo &rhs) const {
            return
                    (this->particles[0] == rhs.particles[0] && this->particles[1] == rhs.particles[1])
                    || (this->particles[1] == rhs.particles[0] && this->particles[0] == rhs.particles[1]);
        }
    };

    class HairDFTLSimulator;

    struct HairDFTLStrand {
        friend class HairDFTLSimulator;

        size_t particle_count;
        size_t particle_begin_index; //the begin index in global

        std::vector<Eigen::Vector3f> pos_array;
        std::vector<Eigen::Vector3f> vel_array;
        std::vector<Eigen::Vector3f> filtered_pos_array;

        std::vector<Eigen::Vector3f> rest_filtered_pos_array;
        std::vector<float> rest_length;

        std::vector<Eigen::Matrix3f> frames;
        std::vector<Eigen::Vector3f> rel_coords; //relative coordinate to store the index

        HairDFTLStrand() = default;

    private:
        void get_rest_length();
        void get_frames();
        void get_rel_coords();
        void get_pos_array();
        void get_filtered_pos();
    };

    class HairDFTLSimulator {
    public:
        HairDFTLSimulator(const HairReader &hair_reader, const HairDFTLConfiguration &conf_);

        virtual int get_particle_count() const {
            return particle_count;
        }

        virtual int get_strand_count() const {
            return strand_count;
        }

        virtual int get_particle_count_on_strand(int idx) const {
            return strands[idx].particle_count;
        }

        virtual void copy_particles(float *positions) const {
            for (const auto & strand : strands)
                for (const auto & pos : strand.filtered_pos_array) {
                    positions[0] = pos(0);
                    positions[1] = pos(1);
                    positions[2] = pos(2);
                    positions += 3;
                }
        }

        virtual void on_frame(const HairSimulatorData &data);

        virtual ~HairDFTLSimulator();
    private:
        size_t particle_count;
        size_t strand_count;

        //stiction solver
        std::vector<HairDFTLParticleInfo> aligned_x_particle_indexes;
        std::vector<HairDFTLStictionLinkInfo> stiction_springs;

        std::vector<HairDFTLStrand> strands;
        HairDFTLConfiguration conf;

        void resolve_time_intergration(const HairSimulatorData &data);

        inline void resolve_time_intergration_for_strand(HairDFTLStrand &strand, const Eigen::Vector3f &strand_root_pos, const float t);

        void resolve_collision(const HairSimulatorData &data);
        void get_pos_from_filtered();
        void resolve_stiction(const HairSimulatorData &data);

        inline Eigen::Vector3f &get_particle_pos(const HairDFTLParticleInfo &x) {
            return strands[x.strand_idx].pos_array[x.rel_idx];
        }

        inline Eigen::Vector3f &get_particle_vel(const HairDFTLParticleInfo &x) {
            return strands[x.strand_idx].vel_array[x.rel_idx];
        }

        inline Eigen::Vector3f &get_particle_filtered_pos(const HairDFTLParticleInfo &x) {
            return strands[x.strand_idx].filtered_pos_array[x.rel_idx];
        }
    };

}

#endif //HAIRCORE_HAIRDFTLSIMULATOR_H
